# Copyright (c) 2007 Fabio Makoto Akita
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# Author: AkitaOnRails (www.akitaonrails.com)
# Support: Surgeworks LLC (www.surgeworks.com)

# (1) SQLite3 fix for unit test
# http://dev.rubyonrails.org/ticket/4537
#
# (2) Fix for YAML deserialization (down below)
# http://itsignals.cascadia.com.au/?p=10
module ActiveRecord
  module ConnectionAdapters #:nodoc:
    class SQLiteAdapter < AbstractAdapter
      def indexes(table_name, name = nil) #:nodoc:
        result = [] 
        execute("PRAGMA index_list(#{table_name})", name).each do |row| 
          unless row['name'] =~ /^sqlite_autoindex/ 
            index = IndexDefinition.new(table_name, row['name']) 
            index.unique = row['unique'] != '0' 
            index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] } 
            result << index 
          end
        end
        result
      end
    end
  end

  class Migration
    class << self
      alias :old_migrate :migrate
      def migrate(direction)
        old_migrate(direction)
        # ensure that deferred vacuums run at the very end of each migration file
        # could have deferred it to the very end of migration series but this could
        # cause very nasty problem if the newly added columns are needed still inside
        # one of the migration files before vacuuming
        begin
          execute('VACUUM') if direction == :up && Base.connection.adapter_name =~ /^SQLite/ && 
            ActiveRecord::ConnectionAdapters::SQLiteAdapter.migration_vacuum
        ensure
          ActiveRecord::ConnectionAdapters::SQLiteAdapter.migration_vacuum = false
        end
      end
    end
  end

  # Fix for SQLite where the primary key type is incorrectly inferred to be
  # always :integer. 
  class SchemaDumper #:nodoc:
    private
    def table(table, stream)
      columns = @connection.columns(table)
      begin
        tbl = StringIO.new

        if @connection.respond_to?(:pk_and_sequence_for)
          pk, pk_seq = @connection.pk_and_sequence_for(table)
        end
        
        if @connection.respond_to?(:primary_key)
          pk = @connection.primary_key(table)
        end
        pk ||= 'id'

        tbl.print "  create_table #{table.inspect}"
        # only consider the column 'id' as primary key if it is the default Rails' integer
        if columns.detect { |c| c.name == pk and c.type == :integer }
          if pk != 'id'
            tbl.print %Q(, :primary_key => "#{pk}")
          end
        else
          tbl.print ", :id => false"
        end
        tbl.print ", :force => true"
        tbl.puts " do |t|"

        column_specs = columns.map do |column|
          raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
          # only consider the column 'id' as primary key if it is the default Rails' integer
          next if column.name == pk and column.type == :integer
          spec = {}
          spec[:name]    = column.name.inspect
          spec[:type]    = column.name == pk ? "#{column.sql_type} PRIMARY KEY".inspect : column.type.inspect
          spec[:limit]   = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal && column.name != pk
          spec[:precision] = column.precision.inspect if !column.precision.nil?
          spec[:scale] = column.scale.inspect if !column.scale.nil?
          spec[:null]    = 'false' if !column.null
          spec[:default] = default_string(column.default) if !column.default.nil?
          (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
          spec
        end.compact
        keys = [:name, :type, :limit, :precision, :scale, :default, :null] & column_specs.map{ |spec| spec.keys }.inject([]){ |a,b| a | b }
        lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
        format_string = lengths.map{ |len| "%-#{len}s" }.join("")
        column_specs.each do |colspec|
          values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
          tbl.print "    t.column "
          tbl.print((format_string % values).gsub(/,\s*$/, ''))
          tbl.puts
        end

        tbl.puts "  end"
        tbl.puts
        
        indexes(table, tbl)

        tbl.rewind
        stream.print tbl.read
      rescue => e
        stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
        stream.puts "#   #{e.message}"
        stream.puts
      end
      
      stream
    end
  end

  module ConnectionAdapters #:nodoc:
    class TableDefinition
      # the sqlite adapter forces the type of the primary key to integer
      # this fix makes it flexible
      @@default_primary_key_type = :primary_key
      cattr_accessor :default_primary_key_type

      def primary_key(name)
        column(name, default_primary_key_type)
      end
    end

    class SQLiteAdapter
      class << self
        @@migration_vacuum = false
        cattr_accessor :migration_vacuum
      end
      
      def add_column(table_name, column_name, type, options = {}) #:nodoc:
        super(table_name, column_name, type, options)        
        # This is too slow to run on every run, so defer running it very late in the
        # migration process
        # See last paragraph on http://www.sqlite.org/lang_altertable.html
        #execute "VACUUM"
        ActiveRecord::ConnectionAdapters::SQLiteAdapter.migration_vacuum = true
      end
      
      def copy_table(from, to, options = {}) #:nodoc:

        # this fix ensure that the correct primary key type is maintained
        # between migration changes
        pk = primary_key(from)
        pk_col = columns(from).select {|c| c.name == pk }
        pk_col = pk_col.first if pk_col && pk_col.size > 0
      
        old_type = ActiveRecord::ConnectionAdapters::TableDefinition.default_primary_key_type
        ActiveRecord::ConnectionAdapters::TableDefinition.default_primary_key_type = "#{pk_col.sql_type} PRIMARY KEY NOT NULL"
        begin
          create_table(to, options) do |@definition|
            columns(from).each do |column|
              column_name = options[:rename] ?
                (options[:rename][column.name] ||
                 options[:rename][column.name.to_sym] ||
                 column.name) : column.name

              @definition.column(column_name, column.type,
                :limit => column.limit, :default => column.default,
                :null => column.null)
            end
            @definition.primary_key(primary_key(from))
            yield @definition if block_given?
          end
        
        ensure
          ActiveRecord::ConnectionAdapters::TableDefinition.default_primary_key_type = old_type
        end
        
        copy_table_indexes(from, to)
        copy_table_contents(from, to,
          @definition.columns.map {|column| column.name},
          options[:rename] || {})
      end

      def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
        column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
        rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
        from_columns = columns(from).collect {|col| col.name}
        columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
        columns.delete('primary') # if some table has this column, it can cause errors
        @connection.execute "SELECT * FROM #{from}" do |row|
          sql = "INSERT INTO #{to} ("+columns*','+") VALUES ("            
          sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
          sql << ')'
          @connection.execute sql
        end
      end
    end
  end  
end

# (2) Deserialization problem
# http://itsignals.cascadia.com.au/?p=10
YAML.add_domain_type("ActiveRecord,2007", "") do |type, val|
  klass = type.split(':').last.constantize
  YAML.object_maker(klass, val)
end

ActiveRecord::Base.send :class_eval, <<-EOF
  def to_yaml_type
    "!ActiveRecord,2007/\#\{self.class\}"
  end

  def to_yaml_properties
    ['@attributes']
  end
EOF